package com.xh.baselibrary.utils.net;

import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Handler;
import android.os.HandlerThread;
import android.text.TextUtils;

import com.xh.baselibrary.log.XLog;
import com.xh.baselibrary.utils.net.ping.CmdPingPolicy;
import com.xh.baselibrary.utils.net.ping.IPingPolicy;
import com.xh.baselibrary.utils.net.ping.SocketConnectPolicy;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class PingWorker {
    public interface NetListener{
        void onDelay(long log);
        void onError();
    }
    /**
     * Socket 链接超时时间
     * 1s
     */
    public static final int CONNECT_TIMEOUT = 2000;

    /**
     * 链接默认端口号
     */
    private static final int CONNECT_DEFAULT_PORT = 80;

    /**
     * 超时结果
     */
    public static final long CONNECT_TIMEOUT_RESULT = -1L;

    /**
     * 断网结果
     */
    public static final long CONNECT_NET_WORK_BROKEN = 0L;
    /**
     * 备份 host
     */
    private String BACK_HOST;

    /**
     * 初始化延迟
     */
    private long INIT_DELAY = 1L;

    /**
     * 域名集合
     */
    private volatile HostMap<String, Long> hostList;

    /**
     * 当前域名index
     */
    private volatile int currHostIndex = 0;


    private NetListener mListener;

    private HandlerThread mHandlerThread;

    private volatile int DELAY_TIME = 1000;

    private ConnectivityManager manager;

    private Handler mHandler;

    private IPingPolicy mPingPolicy;

    private ConnectCall mConnectCall;

    private static final String TAG = PingWorker.class.getSimpleName();

    public void setScheduleTime(int dalay){
        DELAY_TIME = dalay;
    }
    public PingWorker(Context context,String[] host , String backHost,int pingPolicy,NetListener listener){
        this.mListener = listener;
        this.BACK_HOST =backHost;
        this.hostList = transform(host);
        if(pingPolicy == IPingPolicy.POLICY_SOCKET_CONNECT){
            mPingPolicy = new SocketConnectPolicy();
        }else if (pingPolicy == IPingPolicy.POLICY_SYSTEM_CMD){
            mPingPolicy = new CmdPingPolicy();
        }
        this.manager = (ConnectivityManager) context.getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);
        this.mHandlerThread = new HandlerThread("ping_worker");
        mHandlerThread.start();
        mHandler = new Handler(mHandlerThread.getLooper());

    }

    private HostMap<String, Long> transform(String[] host) {
        if (host == null || host.length == 0)return null;
        HostMap<String,Long> hostCache = new HostMap<>(host.length);
        for (String h : host){
            hostCache.put(h,INIT_DELAY);
        }
        return hostCache;
    }

    private String optHost(){
        if (hostList != null || hostList.isEmpty()) {
            XLog.i(TAG,"hostList is null");
            return BACK_HOST;
        }
        ArrayList<String> keys = new ArrayList<>(hostList.keySet());
        String indexKey = keys.get(currHostIndex >= keys.size()? currHostIndex = 0:currHostIndex++);
        if (indexKey!=null){
            return indexKey;
        }else return BACK_HOST;

    }

    //变更域名当前延迟信息
    private void updateHost(String host,long delay){
        if (hostList == null||hostList.size() == 0) {
            return;
        }
        if (hostList.containsKey(host)){
            hostList.put(host,delay);
        }else {
            XLog.i(TAG,"host list is illegal");
        }
    }
    private class PingTask implements Runnable{

        @Override
        public void run() {
            String host = optHost();
            long dalay = connect(host,CONNECT_DEFAULT_PORT);
            //无网
            if (dalay == CONNECT_NET_WORK_BROKEN){
                if (mListener != null) {
                    mListener.onError();
                }
                //超时，结果也不直接对外回调，交给网络策略判断
            }else if (dalay == CONNECT_TIMEOUT_RESULT){
                if (!TextUtils.equals(host,BACK_HOST)){
                    updateHost(host,-1L);
                }
                if (mListener != null) {
                    mListener.onDelay(dalay);
                }
            }else {
                if (!TextUtils.equals(host,BACK_HOST)){
                    updateHost(host,-1L);
                }
                if (mListener != null) {
                    mListener.onDelay(dalay);
                }
            }
            try {
                if (mHandler != null) {
                    mHandler.removeCallbacksAndMessages(null);
                    mHandler.postDelayed(this,DELAY_TIME);
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
    //定时Ping task
    private PingTask mPingTask;
    //开启网络监控
    public void start(){
        synchronized (this){
            if (!isWorking()){
                if (mPingTask == null) {
                    mPingTask = new PingTask();
                }
                mHandler.post(mPingTask);
                isWorking = true;
            }else {
                XLog.i(TAG,"isWorking!!!");
            }
        }
    }

    public void stop(){
        synchronized (this){
            if (!isWorking){
                return;
            }
            XLog.i(TAG,"stop->");
            if (mHandler!=null && mPingTask !=null){
                mHandler.removeCallbacks(mPingTask);
            }
            mPingTask =null;
            isWorking =false;
        }
    }
    public void release(){
        synchronized (this){
            manager = null;
            if (mHandler != null) {
                mHandler.removeCallbacksAndMessages(null);
            }
            if (mHandlerThread != null) {
                mHandlerThread.getLooper().quitSafely();
            }
            if (executorService != null) {
                executorService.shutdownNow();
            }
            mHandlerThread = null;
            mListener = null;
            isWorking = false;
        }
    }
    private volatile boolean isWorking = false;
    public boolean isWorking(){
        return isWorking;
    }

    private ExecutorService executorService = Executors.newSingleThreadExecutor();
    private long connect(String host, int port) {
        //Tbox上网，系统没有修改好这个策略，基本无效
        if (!isNetworkConnected()){
            XLog.i("isNetworkConnected false --->");
            return CONNECT_NET_WORK_BROKEN;
        }
        if (mConnectCall == null) {
            mConnectCall = new ConnectCall();
        }
        mConnectCall.setHost(host);
        mConnectCall.setPort(port==0?CONNECT_DEFAULT_PORT:port);
        try{
            return executorService.submit(mConnectCall).get(CONNECT_TIMEOUT, TimeUnit.MILLISECONDS);
        }catch (InterruptedException | ExecutionException| TimeoutException e){
            e.printStackTrace();
            return CONNECT_TIMEOUT_RESULT;
        }finally {
            if (mPingPolicy != null) {
                mPingPolicy.close();
            }
        }

    }

    private boolean isNetworkConnected() {
        if (manager != null) {
            NetworkInfo network = manager.getActiveNetworkInfo();
            return !(network==null || !network.isAvailable());
        }
        return false;
    }

    private class HostMap<k,v> extends HashMap<k,v>{
        public HostMap(int initCapacity){
            super(initCapacity);
        }
        public k findKeyByValue(v value){
            Iterator it = entrySet().iterator();
            while (it.hasNext()){
                Entry<k,v> entry = (Entry<k, v>) it.next();
                if (entry.getValue() == value){
                    return entry.getKey();
                }
            }
            return null;
        }

    }

    private class ConnectCall implements Callable<Long> {
        private String host;
        private int port;



        @Override
        public Long call() throws Exception {
            if (mPingPolicy != null) {
                return mPingPolicy.ping(host,port);
            }
            return CONNECT_TIMEOUT_RESULT;
        }

        public void setHost(String host) {
            this.host = host;
        }

        public void setPort(int port) {
            this.port = port;
        }
    }
}
